/** 一个延迟操作DOM的小工具， 主要用于浏览器插件
 * 它可以设置并完成一系列的动作
 */

// v2版本 类名为None 
/**
 * await none.delay(100,200).click('#id').click('#id2').delay().run(); //添加动作到队列，只要队列中有任务就会按顺序去执行。
 */

// 依赖 event.js 

// 单一队列
class Queue {
  constructor() {
    this.current = null;
    this.queue = [];
    this.events = {};
  }
  on(event, fun) {
    if (this.events[event]) {
      this.events[event].push(fun)
    } else {
      this.events[event] = [fun];
    }
  }
  add(data, id = data?.id || None.uuid()) {
    const tmp = this.queue.find(item => item.id === id || item.data === data);
    if (tmp) {
      return tmp;
    }
    const item = { id, data, status: 0, addTime: Date.now(), takeTime: null, takeTimes: 0 };
    if (this.events.add) {
      this.events.add.forEach(fun => {
        fun(item)
      })
    }
    this.queue.push(item);
    return item;
  }
  // 没有ID时取第一个
  get(id) {
    return id ? this.queue.find(item => item.id == id) : this.queue[0];
  }
  has(id) {
    return this.current?.id == id || !!this.queue.find(item => item.id == id);
  }
  setStatus(item, status) {
    item.status = status
  }
  takeOnce() {

  }
  getCurrent() {
    return this.current;
  }
  // 取出队列项进行操作
  take() {
    // FIXME: 单一队列
    if (this.current) {
      this.current.takeTimes++
      return this.current;
    }
    const item = this.queue.shift();
    if (!item) return item;
    item.status = 1;
    item.takeTimes++;
    item.takeTime = Date.now();
    return this.current = item;
  }
  // 回滚
  rollback() {
    this.current.status = 0;
    this.queue.unshift(this.current);
    this.current = null;
    this.takeTime = null;
    this.takeTimes = 0;
  }
  // 移除
  remove(id) {
    if (!id || id === this.current.id) {
      this.current = null;
    } else {
      const idx = this.queue.findIndex(item => item.id === id);
      if (~idx) this.queue.splice(this.queue.slice(idx, 1));
    }
  }
  // 队列是否为空
  isBlank() {
    return !this.queue.length
  }
}

class None {
  constructor(config) {
    this.getMode();
    this.status = 0;  // 0:停止;1:进行中;2:暂停
    this._status = 0; //status 的备份
    this.name = config.name;
    this.deps = {};
    this.queue = [];
    // 与background通信用的端口
    this.port = { postMessage: () => { } };
    this.init();
  }
  static TIMEOUT = 10000; // 10秒
  static uuid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
      var r = Math.random() * 16 | 0,
        v = c == 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
  }
  static sleep(min = 1000, max = min) {
    const t = Math.random() * (max - min) + min;
    console.debug(`等待${t.toFixed(2)}ms`);
    return new Promise(resolve => {
      setTimeout(resolve, t);
    })
  }
  static notice() {

  }

  // 自动化机器人
  bot(match, fun) {
    console.debug(`bot进入当前页${location.href}`);
    let r = false;
    if (!fun) {
      fun = match;
      match = null;
    }
    if (match instanceof RegExp) {
      r = match.test(location.href);
    } else if (match instanceof String) {
      r = location.href == match
    } else if (match instanceof Function) {
      r = match();
    } else if (match instanceof Array) {
      r = match.some(item => location.href.includes(item))
    } else if (!match) {
      r = true;
    }
    r && fun(location);
  }

  // 初始化
  init() {
    const uuid = None.uuid();  // "cg"
    console.debug(`初始化任务:${this.name}`);
    window._useMessage?.(uuid, async (port) => {
      this.port = port;
    });
    // 启用DOM mutation observer 
    this.mutationAdapter(1000);
  }
  // DOM变化时触发的适配器
  mutationAdapter(interval = -1) {
    let time = Date.now();
    const mo = new MutationObserver((e) => {
      // console.debug('触发订阅')
      if (Date.now() - time >= interval) {
        time = Date.now();
        for (let key in this.deps) {
          this.deps[key](e);
        }
      }
    });
    mo.observe(document.body, {
      'subtree': true,
      'characterData': true,
    });
  }
  // 添加 MutationObserver 订阅
  addDep(name, dep) {
    this.deps[name] = dep
  }
  removeDep(name) {
    delete this.deps[name];
  }
  // 获取当前模式
  getMode() {
    // this.port.postMessage({
    //   type:'getMode'
    // })
  }
  // 剪贴板操作，已经废弃
  clipboard(text) {
    // 暂时依赖jQuery
    const $textarea = document.createElement('textarea')
    document.body.appendChild($textarea)
    if (text) {
      $textarea.innerText = text;
      $textarea.select()
      document.execCommand('copy', true)
    } else {
      $textarea.focus();
      this.paste();
      text = $textarea.value;
    }
    $textarea.remove();
    return text;
  }
  // 剪切板操作
  clipboard(text) {
    navigator.clipboard.writeText(text);
  }
  // 异步，需要授权，
  getClipboard() {
    return navigator.clipboard.readText();
  }
  // 粘贴
  paste() {
    document.execCommand('paste');
  }
  pause() {
    this._status = this.status;
    this.status = 2;
    console.debug('暂停')
  }
  resume() {
    this.status = this._status;
    console.debug('继续')
  }
  stop() {
    this.status = 0
    console.debug('停止')
  }
  postMessage(...args) {
    // FIXME: 报错处理
    try {
      this.port?.postMessage?.(...args)
    } catch (e) {
      console.info('postMessage故障', e);
    }
  }

  emit(eventName, data, origin = "*") {
    console.debug(`触发事件:${eventName}`, data);
    this.postMessage({
      type: 'bgEvent',
      event: eventName,
      data,
      origin,
    })
  }

  // bgOn 监听来自background的事件消息
  on(eventName, fun) {
    if (bg_events[eventName]) {
      bg_events[eventName].push(fun);
    } else {
      bg_events[eventName] = [fun];
    }
  }

  bgMsg(type, obj, ...args) {
    const id = None.uuid();
    this.postMessage({
      id,
      type,
      data: obj,
    }, ...args);
    return new Promise(resolve => {
      message_resolve[id] = resolve;
    });
  }
  // 调用background方法
  async bgCall(method, ...args) {
    return this.bgMsg('bgCall', { method, args })
  }
  // 后台Mode
  bgMode() {
    return this.bgMsg('bgMode');
  }
  // 后台Ajax
  bgAjax(obj) {
    const id = None.uuid();
    this.postMessage({
      id,
      cache: obj.cache,
      type: 'bgAjax',
      data: obj,
    });
    return new Promise(resolve => {
      message_resolve[id] = resolve;
    });
  }
  // 后台本地存储
  bgStore(key, value) {
    const id = None.uuid();
    this.postMessage({
      id,
      type: 'bgStore',
      data: {
        key, value
      },
    });
    return new Promise(resolve => {
      message_resolve[id] = resolve;
    });
  }
  // 发送钉钉通知
  dingTalkNotice(msg) {
    // msg = typeof msg === 'object' ? JSON.stringify(msg) : msg;
    this.postMessage({
      type: 'noticeDingTalk',
      // type: 'notice',
      data: msg,
    });
  }
  // 执行队列
  async run(option = { name: None.uuid(), errResume: false }) {
    // 队列的名称
    let name = None.uuid()
    // 遇到错误继续执行
    let errResume = false;
    if (typeof option == 'string') {
      name = option;
    } else {
      name = option.name || name;
      errResume = option.errResume || errResume;
    }
    console.debug('执行', name, '-----------------------')
    return new Promise(async (resolve, reject) => {
      while (this.queue.length) {
        if (this.status === 2 || await none.bgMode() == 1) {
          await None.sleep(1000);
          continue;
        }
        const currentAction = this.queue.shift();
        try {
          await this.execAction(currentAction);
        } catch (e) {
          if (errResume) {
            console.debug('执行动作发生错误，继续执行！', currentAction);
            continue;
          } else {
            this.status = 0;
            console.debug('发生错误，自动终止执行！', currentAction);
            this.dingTalkNotice(`执行任务时发生错误:${JSON.stringify(currentAction)}`);
            // reject(e);
            // 未避免在background页产生error
            resolve(e);
            break;
          }
        }
      }
      this.status = 0;
      resolve(name);
    })
  }
  async execAction(action) {
    await this['_' + action.name](...action.args);
  }
  // 向队列中添加一个操作
  addAction(name, ...args) {
    this.queue.push({ name, args });
    return this;
  }
  // 点击
  click(selector, timeout) {
    return this.addAction('click', selector, timeout);
  }
  // 查询
  query(selector, timeout) {
    return this.addAction('query', selector, timeout);
  }
  input(selector, text) {
    return this.addAction('input', selector, text);
  }
  typing(selector, text) {
    return this.addAction('typing', selector, text);
  }
  focus(selector) {
    return this.addAction('focus', selector);
  }
  // 延迟
  delay(...args) {
    return this.addAction('delay', ...args);
  }

  // 当元素出现时执行handler一次
  onceElementShow(selector, handler) {
    this._query(selector, 0).then(handler)
  }
  async _click(selector, timeout) {
    const el = await this._query(selector, timeout);
    el.click();
  }
  // 键入
  async _typing(selector, text) {
    const el = await this._query(selector);
    // 保存当前剪切板状态
    const tmp = this.clipboard();
    this.clipboard(text);
    el.focus();
    this.paste();
    tmp && this.clipboard(tmp);
  }
  async _focus(selector) {
    const el = await this._query(selector);
    el.focus();
  }
  // 查询
  _query(selector, timeout = None.TIMEOUT) {
    const depName = None.uuid();
    let _resolve = () => { };
    let tid = null;
    console.debug(`开始查找元素'${selector}'`);
    const waitExist = (selector) => {
      const el = $(selector)[0];
      if (el) {
        console.debug(`找到元素'${selector}'`);
        clearTimeout(tid);
        this.removeDep(depName);
        _resolve(el);
      } else {
        this.addDep(depName, () => waitExist(selector));
        // _resolve(false);
      }
    }
    return new Promise((resolve, reject) => {
      _resolve = resolve;
      const over = () => {
        this.removeDep(depName);
        const msg = `查询超时${timeout}ms,选择器:'${selector}'`;
        console.debug(msg);
        resolve(false);
        // reject(msg);
      }
      if (timeout > 0) {
        tid = setTimeout(over, timeout)
      }
      waitExist(selector);
    })
  }
  _delay(min = 1000, max = min) {
    const t = Math.random() * (max - min) + min;
    console.debug(`等待 [${t.toFixed(2)}ms]`);
    return new Promise(resolve => {
      setTimeout(resolve, t);
    })
  }
  async _input(selector, text) {
    const el = await this._query(selector);
    el.value = text;
  }
}
